package cn.cnsy123.shorturls.business.sql.interceptor;

import java.math.BigDecimal;
import java.text.DateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Properties;

import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Component;

import cn.cnsy123.shorturls.api.controller.param.SysSqlExecuteLogInsertParam;
import cn.cnsy123.shorturls.business.event.SysSqlExecuteLogEvent;
import cn.cnsy123.shorturls.constants.CommonConstants;
@Intercepts(value = 
{@Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class }),
 @Signature(type = Executor.class,method = "query",args = {MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class,CacheKey.class,BoundSql.class}),
 @Signature(type = Executor.class,method = "query",args = {MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class})
})
@Component
public class SysSqlExecuteLogInterceptor implements Interceptor,ApplicationEventPublisherAware {
    
    private static final Logger logger = LoggerFactory.getLogger(SysSqlExecuteLogInterceptor.class);
    
    private ApplicationEventPublisher applicationEventPublisher;
    
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];  
        Object parameter = null;  
        if (invocation.getArgs().length > 1) {  
            parameter = invocation.getArgs()[1];  
        }  
        BoundSql boundSql = mappedStatement.getBoundSql(parameter);  
        Configuration configuration = mappedStatement.getConfiguration();  
   
        // 获取sql语句
        String sql = getSql(configuration, boundSql);
        long startTime = System.currentTimeMillis();
        Object result = invocation.proceed();
        long endTime = System.currentTimeMillis();
        if(logger.isDebugEnabled()) {
            logger.debug("Mybatis 拦截器获取SQL:{}",sql);
        }
        
        if(sql.indexOf(CommonConstants.SQL_EXECUTE_LOG_TABLE_NAME) == -1) {
            SysSqlExecuteLogInsertParam sysSqlExecuteLogInsertParam = new SysSqlExecuteLogInsertParam();
            sysSqlExecuteLogInsertParam.setTakeUpTime(new BigDecimal((endTime-startTime)/1000).setScale(2));
            sysSqlExecuteLogInsertParam.setOriginalSql(sql);
            sysSqlExecuteLogInsertParam.setExecuteTime(new Date(System.currentTimeMillis()));
            try {
                SysSqlExecuteLogEvent sysSqlExecuteLogEvent = new SysSqlExecuteLogEvent(sysSqlExecuteLogInsertParam);
                applicationEventPublisher.publishEvent(sysSqlExecuteLogEvent);
                if(logger.isDebugEnabled()) {
                    logger.info("保存-SQL执行记录成功");
                }
            } catch (Exception e) {
                logger.error("保存SQL执行记录-发生异常",e);
            }
        }
        
        return result;
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        
    }
    
    /**
     * 获取SQL
     * @param configuration
     * @param boundSql
     * @return
     */
    private String getSql(Configuration configuration, BoundSql boundSql) {
        Object parameterObject = boundSql.getParameterObject();  
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();  
        String sql = boundSql.getSql().replaceAll("[\\s]+", " ");  
        if (parameterObject == null || parameterMappings.size() == 0) {
            return sql;
        }  
        TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();  
        if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {  
            sql = sql.replaceFirst("\\?", getParameterValue(parameterObject));  
        } else {  
            MetaObject metaObject = configuration.newMetaObject(parameterObject);  
            for (ParameterMapping parameterMapping : parameterMappings) {  
                String propertyName = parameterMapping.getProperty();  
                if (metaObject.hasGetter(propertyName)) {  
                    Object obj = metaObject.getValue(propertyName);  
                    sql = sql.replaceFirst("\\?", getParameterValue(obj));  
                } else if (boundSql.hasAdditionalParameter(propertyName)) {  
                    Object obj = boundSql.getAdditionalParameter(propertyName);  
                    sql = sql.replaceFirst("\\?", getParameterValue(obj));  
                }  
            }  
        }  
        return sql;
    }

    private String getParameterValue(Object obj) {  
        String value = null;  
        if (obj instanceof String) {  
            value = "'" + obj.toString() + "'";  
        } else if (obj instanceof Date) {  
            DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);  
            value = "'" + formatter.format(obj) + "'";  
        } else {  
            if (obj != null) {  
                value = obj.toString();  
            } else {  
                value = "";  
            }  
        }  
        return value;  
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    } 
}
